/*
 * @Description: 程序入口文件
 * @Author: YangHeng
 * @Date: 2021-08-09 13:47:53
 * @FilePath: \web_server_app\index.js
 */

const http = require('http');
const Koa = require('koa');
const app = new Koa();
const path = require('path');
const range = require('koa-range');
const coreUtil = require('core-util-is');
const { isFunction } = coreUtil;
const fs = require('fs-extra')

module.exports = class {
  constructor(option) {
    global.civi = this
    Object.assign(this, coreUtil)
    // 导入扩展
    let extPath = path.join(option.APP_PATH, 'extend/civi.js')
    if (fs.existsSync(extPath)) {
      let extend = require(extPath)
      Object.assign(this, extend)
    }

    Object.assign(this, option)
    this.isDev = this.env === 'development'
    this.app = app

    // 读取配置文件
    this.config = require(path.join(this.ROOT_PATH, 'cv.config.js'))

    this.mock_context = require('./mock_context')
  }
  start(callback) {
    // 执行初始化
    this.init().then(res => {
      this.httpServer = http.createServer(app.callback()).listen(this.config.service.port, this.config.service.host, async () => {
        if (civi.config.wss) {
          // 创建WebSocket服务
          this.logs.info('Create a WebSocket service')
          const Wss = require('./websocket/')
          this.wss = new Wss(this.httpServer, civi.config.wss)
        }

        this.logs.success('Service started successfully')
        this.logs.success('Service runs in', `http://127.0.0.1:${this.config.service.port}/`)

        // 触发回调函数
        callback && callback()

        // 查找启动文件是否存在
        let startup_path = path.join(civi.APP_PATH, 'startup/index.js')
        if (fs.existsSync(startup_path)) require(startup_path)()
      });
    })
  }
  async init() {
    // 加载日志
    this.logs = require('./logger')

    // 导入控制器类
    this.logs.info('load controller class')
    this.Controller = require('./controller')

    // 加载全部控制器
    await this.loadController()

    // 加载缓存
    this.logs.info('load cache')
    this.cache = require('./cache')

    if (this.config.mongo) { // 存在数据库配置才连接数据库
      // 链接数据库
      this.logs.info('Connect to mongo database')
      this.DB = require('./mongoose/model')
      this.db = this.DB.getModels

      // 加载数据库模型
      this.logs.info('load database model')
      await this.loadModel()
    }

    // 导入路由表
    this.logs.info('import routing table')
    try {
      this.routerTable = require(path.join(civi.APP_PATH, 'config/router.js'))
    } catch (err) {
      this.routerTable = []
    }

    // 预加载中间件
    this.logs.info('load middleware')
    app.use(range)

    // 加载中间件
    this.Middleware = require('./middleware')
    this.Middleware = this.Middleware || []
    this.Middleware.forEach(item => {
      if (item.enable === false) return // 过滤未启用的中间件
      if (isFunction(item.handle)) app.use(item.handle)
      else {
        app.use(require('./middleware/' + item.handle)(item.options))
      }
    })

    // 加载Action
    this.logs.info('load Action')
    this.action = require('./action').run
  }
  uuid(type = 'v1') {
    let uuid = require('uuid')
    return uuid[type]()
  }
  async loadModel() {
    let modelDir = path.join(civi.APP_PATH, 'model')
    if (fs.existsSync(modelDir)) {
      let model_list = fs.readdirSync(modelDir)
      model_list.forEach(model => {
        let name = model.split('.js')[0]
        let Model = require(path.join(modelDir, model))
        if (Model && Model.isModel) new Model(name)
      })
    }
  }
  /**
   * 加载控制器
   */
  async loadController() {
    // 读取控制器文件夹
    let baseDir = path.join(civi.APP_PATH, 'controller');
    let controllerArr = [];
    await (async function _iter(_path) {
      let stat = await fs.stat(_path)
      if (stat.isDirectory()) {
        let dirs = await fs.readdir(_path)
        let directory = []
        for (let item of dirs) {
          let stat = await fs.stat(path.join(_path, item))
          if (stat.isDirectory()) directory.push(item)
          else {
            let pathname = path.join(_path, item)
            let cont = require(pathname)
            let func = Object.getOwnPropertyNames(cont.prototype)
            for (let action of func) {
              if (action.lastIndexOf('Action') !== -1) {
                let name = action.substr(0, action.lastIndexOf('Action'))
                let data = { controller: cont, action: name, name: [] }
                let contName = pathname.replace(baseDir + path.sep, '').replace('.js', '').replace(/\\/g, '/')
                let fileName = path.basename(pathname).replace('.js', '')
                data.name.push(contName + '/' + name)
                if (name === 'index') data.name.push(contName)
                if (fileName === 'index') {
                  let _name = contName.substr(0, contName.lastIndexOf('/index'))
                  if (name !== 'index') _name += '/' + name
                  data.name.push(_name)
                }
                controllerArr.unshift(data)
              }
            }
          }
        }

        for (let item of directory) {
          await _iter(path.join(_path, item))
        }
      } else {
      }
    })(baseDir)
    this.controllerList = controllerArr
  }
}